package com.fucking.great.context;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.provider.BeanValueProvider;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import cn.hutool.system.HostInfo;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import com.fucking.great.context.constant.EnvConstant;
import com.fucking.great.context.constant.HttpConstant;
import com.fucking.great.context.manager.PlugManager;
import com.fucking.great.context.manager.PlugManagerFactory;
import com.fucking.great.context.util.PrototypeUtil;

import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.*;

/**
 * 这个给用户使用的内容。
 */
public  class TxnContext extends JSONObject {
    ///////////////////////////////////////////
    //            public static final
    //////////////////////////////////////////
    /** 服务器ip */
    public static final String SERVICE_IP;
    static {

        // docker 容器启动,获得不到宿主机的ip，通过shell脚本的环境变量传递。
        String hostip = SystemUtil.get(EnvConstant.HOSTIP_KEY);
        // 获取本机IP
        if (StrUtil.isBlank(hostip)) {
            OsInfo osInfo = SystemUtil.getOsInfo();
            if (osInfo.isLinux()) {
                NetworkInterface eth0 = NetUtil.getNetworkInterface("eth0");
                List<InterfaceAddress> interfaceAddresses = eth0.getInterfaceAddresses();
                hostip = interfaceAddresses.get(0).getAddress().getHostAddress();
            } else if (osInfo.isMac()) {
//				hostip = NetUtil.getLocalMacAddress();
                HostInfo hostInfo = SystemUtil.getHostInfo();
                hostip = hostInfo.getAddress();
            } else if (osInfo.isWindows()){
                HostInfo hostInfo = SystemUtil.getHostInfo();
                hostip = hostInfo.getAddress();
            }else{
                HostInfo hostInfo = SystemUtil.getHostInfo();
                hostip = hostInfo.getAddress();
            }

        }
        SERVICE_IP = hostip;
    }
    /** 是否属于服务器 */
    public static final boolean IS_SERVICE;
    static{
        String value = SystemUtil.get(EnvConstant.IS_SERVICE_KEY);
        if (value == null) {
            // 用户不设置，就判断是不是linux
            IS_SERVICE = SystemUtil.getOsInfo().isLinux();
        } else {
            // 用户设置，默认是不是服务器
            value = value.trim().toLowerCase();
            IS_SERVICE = value.isEmpty() ? false : Convert.toBool(value, false);
        }
    }

    ///////////////////////////////////////////
    //            private static final
    //////////////////////////////////////////
    private static final Log log = LogFactory.get();
    /** 线程对象. */
    private static final ThreadLocal<TxnContext> TXN_CONTEXT = new ThreadLocal<>();

    ///////////////////////////////////////////
    //            private final
    //////////////////////////////////////////
    /** 暂存的内容.(保存的是执行对象). */
    private  final Map<String,Object> zancunMap =new HashMap<>();


    ///////////////////////////////////////////
    //            private
    //////////////////////////////////////////
    /** 请求头 */
    private final JSONObject header = new JSONObject();
    /**
     * 响应头<br/>
     * 建议用http的响应头,统一规范含义和内容。
     */
    private final JSONObject responseHeader = new JSONObject();
    /** 日志内容 */
    private final List<String> logList = new ArrayList<String>();
    /** 组件管理者 */
    private final PlugManager plugManager;





    /** 日志级别,默认不开启*/
    private int logLevel = 100;
    private String clientIp;
    private String path;
    private String type;
    private Locale locale = Locale.getDefault();
    private String url;
    private String errorCode;
    private String errorMessage;
    private String systemName = ""; // 支持不同的服务商
    private String appid = "";      // 同一服务商,支持多租户. 及appid标识。
    private Log logger = log;




    private TxnContext() {
        if( TXN_CONTEXT.get() != null){
            throw new UtilException("只能该交易中只能init一次");
        }else{
            TXN_CONTEXT.set(this);
        }
        this.plugManager = PrototypeUtil.getPrototypeInstance(PlugManagerFactory.class);
    }

    public TxnContext(PlugManager plugManager) {
        this.plugManager = plugManager;
    }



    ///////////////////////////////////////////
    //            public static  method
    //////////////////////////////////////////



    /** 获得 TxnContext */
    public static TxnContext  getContext(){
        TxnContext txnContext = TXN_CONTEXT.get();
        return txnContext==null ? new TxnContext() : txnContext ;
    }
    /** 用于多线程,的上下文传递,保证线程安全 */
    public static TxnContext  copyContext(TxnContext context){
        TxnContext target = getContext();
        copyContext(context, target);
        return target;
    }

    /**
     *
     * 把src里面所有的属性，copy到target里面去。
     * 会复制的:
     *    appid,system,zancunMap,header...
     * 不会复制的:
     *    logList和json内容。
     * 但是里面的json内容不会复制.需要手工去复制一下。
     * <pre>
     *     src.set("key","你好");
     *     target.getStr("key")  =》 null
     * </pre>
     *
     * @param src
     * @param target
     */
    public static void  copyContext(TxnContext src,TxnContext target){
        BeanValueProvider beanValueProvider = new BeanValueProvider(src, false, true);
        BeanUtil.copyProperties(beanValueProvider,target);
        BeanUtil.copyProperties(src.zancunMap,target.zancunMap);
        BeanUtil.copyProperties(src.header,target.header);
        BeanUtil.copyProperties(src.responseHeader,target.responseHeader);
    }
    /** 用于多线程,的上下文传递,始终都是同一个context,线程不安全. */
    public static void setContext(TxnContext context ){
        TXN_CONTEXT.set(context);
    }
    /**结束释放线程绑定*/
    public void endContext(){
        // 最后这个移除
        TXN_CONTEXT.remove();
    }





    ///////////////////////////////////////////
    //            public  method
    //////////////////////////////////////////
    /** 是否有异常. */
    public boolean hasException(){return this.getErrorCode() != null && this.getErrorMessage() != null; }
    /**
     * 放置:响应头
     * @param key
     * @param obj
     */
    public void putResponseHeader(String key,Object obj){
        this.responseHeader.set(key,obj );
    }
    /**
     * 设置响应头,后面的文档属于什么MIME类型<br/>
     * @param contentType
     */
    public void setResponseContentType(String contentType){
        this.putResponseHeader(HttpConstant.CONTENT_TYPE,contentType );
    }

    /**
     * 暂存管理
     * @param key
     * @param obj
     */
    public void putZancun(String key,Object obj){
        this.zancunMap.put(key,obj );
    }

    //暂存管理
    public Object getZancun(String key){
       return this.zancunMap.get(key );
    }

    public  void trace(  String text, Object... args){appendLogs(0,text,args);}
    public  void debug(  String text, Object... args){appendLogs(1,text,args);}
    public  void info(  String text, Object... args){appendLogs(2,text,args);}
    public  void warn(  String text, Object... args){appendLogs(3,text,args);}
    public  void error(  String text, Object... args){appendLogs(4,text,args);}
    public  void fatal(  String text, Object... args){appendLogs(5,text,args);}
    private  void appendLogs( int level , String text, Object... args){
        TxnContext txnContext = this;
        if(level>txnContext.logLevel){
            txnContext.logList.add(StrUtil.format(text,args));
        }
    }

    /**主要用于获得配置文件时，可以从用户输入的参数获得.
     * 移除key对应的内容，
     * 先看是不是json格式的，
     * {
     *     tyep : "xpath"
     *     value : "wodeparam"
     * }, 是这个格式就从xpath里面取数据。
     *
     * 不是json格式就直接获得对应的内容.
     * @param conf
     * @param expressionKey key()
     * @return
     */
    public String removeConfByPath(JSONObject conf,String expressionKey) {
        return this.getConfByPath(conf,expressionKey,null,true);
    }
    /**
     * 获得(移除)key对应的内容，
     * 先看是不是json格式的，
     * {
     *     tyep : "xpath"
     *     value : "wodeparam"
     * }, 是这个格式就从xpath里面取数据。
     *
     * 不是json格式就直接获得对应的内容.
     * @param conf
     * @param expressionKey key()
     * @param defaultValue 默认值
     * @param isRemove   是否移除
     * @return
     */
    public String getConfByPath(JSONObject conf,String expressionKey, String defaultValue,boolean isRemove) {
        JSONObject jsonObject;
        try {
            jsonObject = conf.getJSONObject(expressionKey);
        } catch (Exception e) {
            jsonObject = null;
        }
        //

        if(jsonObject == null){
            String retValue ;
            if(isRemove){
                Object remove = conf.remove(expressionKey);
                retValue =   remove == null ? defaultValue : String.valueOf(remove);
            }else{
                retValue = conf.getStr(expressionKey, defaultValue);
            }
            return retValue;
        }else{
            //
            expressionKey = isRemove ? String.valueOf(jsonObject.remove("value")) : conf.getStr("value", defaultValue);
            Object byPath = this.getByPath(expressionKey);
            return  byPath == null ? defaultValue : String.valueOf(byPath);
        }
    }

    public String showResult(Object result) {
        JSONObject jsonObject =new JSONObject();
        jsonObject.putOpt("errCode",this.getErrorCode());
        jsonObject.putOpt("errMsg",this.getErrorMessage());
        jsonObject.putOpt("data" , result );
        List<String> logList = this.getLogList();
        if(logList.size()>0){
            jsonObject.set("runlog", logList);
        }
        return jsonObject.toString();
    }

    public void putHeader(Map<String, String> headerMap){
        header.putAll(headerMap);
    }

    ///////////////////////////////////////////
    //            @Override  method
    //////////////////////////////////////////
    @Override
    public JSONObject getJSONObject(String key) {
        JSONObject jsonObject = super.getJSONObject(key);
        return jsonObject == null ? new JSONObject() : jsonObject;
    }



    ///////////////////////////////////////////
    //            get set  method
    //////////////////////////////////////////


    public int getLogLevel() {
        return logLevel;
    }

    public void setLogLevel(int logLevel) {
        this.logLevel = logLevel;
    }

    public String getClientIp() {
        return clientIp;
    }

    public void setClientIp(String clientIp) {
        this.clientIp = clientIp;
    }

    public JSONObject getHeader() {
        return header;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Locale getLocale() {
        return locale;
    }

    public void setLocale(Locale locale) {
        this.locale = locale;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
    public String getSystemName() {
        return systemName;
    }

    public void setSystemName(String systemName) {
        this.systemName = systemName;
    }

    public List<String> getLogList() {
        return logList;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public PlugManager getPlugManager() {
        return plugManager;
    }

    public Log getLogger() {
        return logger;
    }

    public void setLogger(Log logger) {
        this.logger = logger;
    }

    public JSONObject getResponseHeader() {
        return responseHeader;
    }

}